﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using ZOIL.Framework.Core;
using ZOIL.Framework.Presentation;

namespace ZOIL.Library.Devices
{
    public class TouchInputBehavior : ZInputBehavior<ZInformationLandscape>
    {
        #region private fields

        // TODO: replace using TransformToAncestor()
        private IEnumerable<ZInformationLandscape> _parentLandscapes = new List<ZInformationLandscape>();

        private bool _selectionActive;
        private int _selectionContactId;
        private int _lastClickZoomTimestamp;

        private Dictionary<int, Dictionary<object, object>> _touchDeviceUserData = new Dictionary<int, Dictionary<object, object>>();

        #endregion

        #region public fields

        public bool IsSelecting = false;

        #endregion

        #region public properties

        private Point _origin;
        public Point Origin
        {
            get
            {
                return _origin;
            }
            set
            {
                _origin = value;
                _origin.X -= 25;
                _origin.Y -= 25;
                RaisePropertyChanged("Origin");
            }
        }

        #endregion

        #region dependency properties

        #region IsInertiaEnabled

        public bool IsInertiaEnabled
        {
            get { return (bool)GetValue(IsInertiaEnabledProperty); }
            set { SetValue(IsInertiaEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsInertiaEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsInertiaEnabledProperty =
            DependencyProperty.Register("IsInertiaEnabled", typeof(bool), typeof(TouchInputBehavior), new PropertyMetadata(true));

        #endregion

        #endregion

        #region method overrides

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.IsManipulationEnabled = true;

            AssociatedObject.TouchDown += OnTouchDown;
            AssociatedObject.TouchMove += OnTouchMove;
            AssociatedObject.TouchUp += OnTouchUp;

            AssociatedObject.ManipulationStarting += OnManipulationStarting;
            AssociatedObject.ManipulationDelta += OnManipulationDelta;
            AssociatedObject.ManipulationInertiaStarting += OnManipulationInertiaStarting;
            AssociatedObject.ManipulationCompleted += ManipulationCompleted;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.TouchDown -= OnTouchDown;
            AssociatedObject.TouchMove -= OnTouchMove;
            AssociatedObject.TouchUp -= OnTouchUp;

            AssociatedObject.ManipulationStarting -= OnManipulationStarting;
            AssociatedObject.ManipulationDelta -= OnManipulationDelta;
            AssociatedObject.ManipulationInertiaStarting -= OnManipulationInertiaStarting;
            AssociatedObject.ManipulationCompleted -= ManipulationCompleted;

            base.OnDetaching();
        }

        #endregion

        #region event handling

        private readonly object _clickZoomTimestampKey = new object();
        private readonly object _clickZoomSourceKey = new object();
        private readonly object _clickZoomSenderKey = new object();
        private readonly object _clickZoomInitialPoint = new object();
        private readonly object _clickZoomActiveKey = new object();

        #region Touch Events

        private void OnTouchDown(object sender, TouchEventArgs e)
        {
            if (!IsEnabled) return;

            if (!IsSelecting)
            {
                //save position in order to prepare for click zoom
                if (IsClickZoomEnabled)
                {
                    var id = e.TouchDevice.Id;
                    if (!_touchDeviceUserData.ContainsKey(id))
                        _touchDeviceUserData.Add(id, new Dictionary<object, object>());

                    var userData = _touchDeviceUserData[id];

                    userData[_clickZoomTimestampKey] = Environment.TickCount;
                    userData[_clickZoomSourceKey] = e.Source as FrameworkElement;
                    userData[_clickZoomSenderKey] = sender as FrameworkElement;
                    userData[_clickZoomInitialPoint] = e.TouchDevice.GetTouchPoint(null).Position;
                }
                //Find all parent Informationlandscapes
                // TODO: replace using TransformToAncestor()
                _parentLandscapes = ZTreeHelper.TryFindParents<ZInformationLandscape>(AssociatedObject);
            }
            else
            {
                if (!_selectionActive)
                {
                    e.TouchDevice.Capture(AssociatedObject);
                    _selectionActive = true;
                    _selectionContactId = e.TouchDevice.Id;
                    e.Handled = true;
                }
            }
        }
        private void OnTouchMove(object sender, TouchEventArgs e)
        {
            if (!IsEnabled) return;

            if (IsSelecting && e.TouchDevice.Id == _selectionContactId)
            {
                e.Handled = true;
            }
        }

        private void OnTouchUp(object sender, TouchEventArgs e)
        {
            if (!IsEnabled) return;

            var id = e.TouchDevice.Id;

            if (IsClickZoomEnabled && !IsSelecting)
            {
                if (!_touchDeviceUserData.ContainsKey(id))
                    return;

                var userData = _touchDeviceUserData[id];

                var clickZoomSender = userData[_clickZoomSenderKey];
                if (clickZoomSender == null) return;

                var clickZoomSource = userData[_clickZoomSourceKey];
                if (clickZoomSource == null) return;

                var tickCount = userData[_clickZoomTimestampKey];
                if (tickCount == null) return;

                var initialPoint = userData[_clickZoomInitialPoint];
                if (initialPoint == null) return;

                //first check if the finger has been pressed a given amount of time
                if ((Environment.TickCount - (int)tickCount) > 200)
                    return;

                var touchPoint = e.TouchDevice.GetTouchPoint(null);
                var panDistance = Point.Subtract(touchPoint.Position, (Point)initialPoint).Length;

                if (panDistance > 10)
                    return;

                _lastClickZoomTimestamp = Environment.TickCount;

                DoClickZoom((FrameworkElement)clickZoomSender, (FrameworkElement)clickZoomSource);

                e.Handled = true;
                return;
            }

            if (IsSelecting && e.TouchDevice.Id == _selectionContactId)
            {
                e.TouchDevice.Capture(AssociatedObject, CaptureMode.None);
                _selectionActive = false;
                e.Handled = true;
            }

            //Wenn nicht e.Handled = true; dann übernehmen die Manipulation-Events
            /*if (isClickZoomEnabled && isPanningEnabled && isScalingEnabled)
            {
                e.Handled = true;
            }*/

            if (_touchDeviceUserData.ContainsKey(id))
                _touchDeviceUserData.Remove(id);
        }

#endregion

        #region Manipulation

        void OnManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            if (!IsEnabled) return;

            if (e.OriginalSource != this && !Equals(e.OriginalSource, AssociatedObject))
                return;

            e.ManipulationContainer = CurrentWindow;
            e.Handled = true;
        }

        void OnManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            if (!IsEnabled) return;

            // http://msdn.microsoft.com/en-us/library/ee649090.aspx
            // Decrease the velocity of the Rectangle's movement by 
            // 10 inches per second every second.
            // (10 inches * 96 pixels per inch / 1000ms^2)
            e.TranslationBehavior.DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0);

            e.Handled = true;
        }

        void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            if (!IsInertiaEnabled && e.IsInertial)
                e.Complete();

            if (!IsEnabled) return;

            if (e.OriginalSource != this && !Equals(e.OriginalSource, AssociatedObject))
                return;

            if (IsSelecting) return;

            if (IsPanningEnabled)
            {
                var scaleValue = AssociatedObject.Animation.Position.Zoom;

                // TODO: replace using TransformToAncestor()
                //look for all parent Informationlandscapes and adjust the scale factor
                foreach (var parentLandscape in _parentLandscapes)
                {
                    var scale = parentLandscape.Animation.Position.Zoom;
                    scaleValue *= scale;
                }

                var panningAllowed = (e.DeltaManipulation.Translation.X != 0 || e.DeltaManipulation.Translation.Y != 0);

                panningAllowed = panningAllowed && (Environment.TickCount - _lastClickZoomTimestamp > 500);

                if (panningAllowed)
                    AssociatedObject.PanBy(new Vector(e.DeltaManipulation.Translation.X / scaleValue, e.DeltaManipulation.Translation.Y / scaleValue), false);
            }

            if (IsZoomingEnabled && e.DeltaManipulation.Scale.X != 1)
            {
                Origin = new Point(e.ManipulationOrigin.X, e.ManipulationOrigin.Y);
                //TODO Surface 2 Migration: scale should be constructed by x and y
                AssociatedObject.ZoomBy(CurrentWindow.TranslatePoint(Origin, AssociatedObject), e.DeltaManipulation.Scale.X, false);
            }

            e.Handled = true;
        }

        void ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            if (!IsEnabled) return;

            if (e.OriginalSource != this && !Equals(e.OriginalSource, AssociatedObject))
                return;
        }

        #endregion

        #region Click Zoom

        private void DoClickZoom(FrameworkElement sender, Visual source)
        {
            var zoomTarget = ZTreeHelper.GetClosestZoomTarget(sender, source);
            RaiseClickZoomEvent(zoomTarget);
        }

        #endregion

        #endregion
    }
}
